package top.abilities.util.util;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.dataformat.csv.CsvMapper;
import com.fasterxml.jackson.dataformat.csv.CsvSchema;

import java.io.IOException;
import java.io.OutputStream;
import java.io.Reader;
import java.io.Writer;
import java.nio.charset.Charset;
import java.nio.charset.MalformedInputException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Iterator;
import java.util.List;

/**
 * @Auhtor: ciro
 * @Description
 * @Date: 2017/11/20 11:55
 * @Modified By:
 */
public class Jsons {
    public final static ObjectMapper mapper =new ObjectMapper()
            .enable(SerializationFeature.INDENT_OUTPUT, SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS);


    public static <T> T read(Path p, Class<T> clz) throws IOException {
        return mapper.readValue(Files.readAllBytes(p), clz);
    }


    public static <T> T read(String s, Class<T> clz) throws IOException {
        return read(Paths.get(s), clz);
    }

    public static <T> T read(byte[] a, Class<T> clz) throws IOException {
        return mapper.readValue(a, clz);
    }


    public static void write(Path p, Object o) throws IOException {
        try (OutputStream os = Files.newOutputStream(p)) {
            mapper.writeValue(os, o);
        }
    }


    public static void prettyWrite(String p, Object o) throws IOException {
        Path p0 = Paths.get(p);
        if (!Files.isDirectory(p0.getParent())) {
            Files.createDirectories(p0.getParent());
        }
        prettyWrite(Paths.get(p), o);
    }

    public static void prettyWrite(Path p, Object o) throws IOException {
        try (OutputStream os = Files.newOutputStream(p)) {
            mapper.writeValue(os, o);
        }
    }

    public static <T> T clone(T o) {
        try {
            JsonNode node = mapper.valueToTree(o);
            return mapper.treeToValue(node, (Class<T>) o.getClass());
        } catch (JsonProcessingException ex) {
            ex.printStackTrace();
            return null;
        }
    }


    //
    // reference URL:
    //   http://stackoverflow.com/questions/9895041/merging-two-json-documents-using-jackson
    //
    public static JsonNode extend(JsonNode mainNode, JsonNode updateNode) {
        if (updateNode == null) {
            return mainNode;
        }
        if (mainNode == null) {
            return updateNode;
        }

        Iterator<String> fieldNames = updateNode.fieldNames();
        while (fieldNames.hasNext()) {

            String fieldName = fieldNames.next();
            JsonNode jsonNode = mainNode.get(fieldName);

            if (jsonNode != null && jsonNode.isObject()) {
                extend(jsonNode, updateNode.get(fieldName));
            } else {
                if (mainNode instanceof ObjectNode) {
                    // Overwrite field
                    JsonNode value = updateNode.get(fieldName);
                    //当权限高级的配置文件里未指定值的key-value时，用最底层的
                    if (value.toString().equals("null"))
                        continue;
                    ((ObjectNode) mainNode).set(fieldName, value);
                }
            }

        }

        return mainNode;
    }


    public static JsonNode extend(JsonNode base, JsonNode... nodes) {
        if (nodes.length == 0) {
            return base;
        }

        JsonNode res = base;
        for (JsonNode node : nodes) {
            res = extend(res, node);
        }
        return res;
    }

    public static JsonNode extend(JsonNode base, Object... objs) {
        if (objs == null || objs.length == 0) {
            return base;
        }

        JsonNode nodes[] = new JsonNode[objs.length];
        for (int i = 0; i < objs.length; ++i) {
            if (objs[i] == null) {
                nodes[i] = null;
            } else if (objs[i] instanceof JsonNode) {
                nodes[i] = (JsonNode) objs[i];
            } else {
                nodes[i] = mapper.valueToTree(objs[i]);
            }
        }
        return extend(base, nodes);
    }

    public static ObjectMapper getInstance() {
        DateFormat myDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        mapper.setDateFormat(myDateFormat);
        return mapper;
    }

    public static String objToJson(Object object) throws JsonProcessingException {
        String json = getInstance().writeValueAsString(object);
        return json;
    }

    public static <T> T jsonToList(String json, Class clazz) throws IOException {
        T list = mapper.readValue(json, mapper.getTypeFactory().constructCollectionType(List.class, clazz));
        return list;
    }

    public static <T> T jsonToObj(String json, Class clazz) throws IOException {
        mapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
        T t = (T) getInstance().readValue(json, clazz);
        return t;
    }

    public static <T> T jTM(String j , Class c) throws IOException {
        if (j.equals("")){
            return null;
        }
        ObjectMapper mapper = new ObjectMapper();
        mapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
        T t = (T) getInstance().readValue(j, c);
        return t;
    }


    public static void writeObjToJsonFile(Object object, String file) throws IOException {
        try (Writer writer = Files.newBufferedWriter(Paths.get(file))) {
            getInstance().writeValue(writer, object);
        }
    }

    public static <T> T readObjFromJsonFile(String file, Class clazz) throws IOException {
        String str = readFile(file);
        return jsonToObj(str, clazz);
    }

    public static String readFile(String path) throws IOException {
        String encoding = "UTF-8";
        byte[] encoded = Files.readAllBytes(Paths.get(path));
        return new String(encoded, encoding);
    }

    public static <T> List<T> readCsv(String fn, Class<T> clazz) throws IOException {
        CsvMapper mapper = new CsvMapper();
        CsvSchema schema = mapper.schemaFor(clazz).withUseHeader(true);
        try (Reader br = Files.newBufferedReader(Paths.get(fn))) {
            List<T> res = (List<T>) mapper.reader(clazz).with(mapper.schemaWithHeader()).readValues(br).<T>readAll();
            return res;
        } catch (MalformedInputException ex) {
            try (Reader br = Files.newBufferedReader(Paths.get(fn), Charset.forName("GBK"))) {
                List<T> res = (List<T>) mapper.reader(clazz).with(mapper.schemaWithHeader()).readValues(br).<T>readAll();
                return res;
            }
        }
    }
}
